import asyncio
import json
import time
import aioredis
import async_timeout
import redis
import tornado.ioloop
import tornado.web
import tornado.websocket
import urllib.parse
import requests


def qinyunke(msg):
    # 定义青云客的路由
    url = 'http://api.qingyunke.com/api.php?key=free&appid=0&msg=%s' % urllib.parse.quote(msg)

    res = requests.get(url)

    return res.json()['content']


# print(qinyunke('别纠结于是与不是，菲菲早已看破红尘~'))


channels = ['channel_1', 'channel_2']

# 用户列表
users = []

import tornado.httpserver
from tornado.options import define

define('port', default=8001, help='default port', type=int)


class MyStacks:
    def __init__(self):
        self.r = redis.Redis(decode_responses=True)
        self.c = redis.Redis(host='192.168.99.100', port=6380, decode_responses=True)

    # 入栈         uid   数据信息
    def push(self, key, item):

        items = json.loads(item)
        uid = int(items['uid'])
        # print(time, type(time))
        res = hash(uid & 1)
        # print(res)
        try:
            if res == 1:
                self.r.rpush(key, item)
            else:
                self.c.rpush(key, item)
        except Exception as e:
            self.r.delete(key)
            self.r.rpush(key, item)

    # 栈全部数据
    def lrange(self, key):

        list1 = self.r.lrange(key, 0, -1)
        list2 = self.c.lrange(key, 0, -1)
        list3 = []
        for i in list1:
            list3.append(json.loads(i))
        for i in list2:
            list3.append(json.loads(i))
        # print(list3)
        # lambda
        list3.sort(key=lambda x: x['time'])
        return json.dumps(list3)

    # 出栈    撤回
    def pop(self, key, id):
        res = hash(int(id) & 1)
        if res == 1:
            self.r.rpop(key)
        else:
            self.c.rpop(key)


# 声明基类
class BaseHandler(tornado.web.RequestHandler):

    # 重写父类方法
    def set_default_headers(self):
        # 设置请求头信息
        print("开始设置")
        # 域名信息
        self.set_header("Access-Control-Allow-Origin", "*")
        # 请求信息
        self.set_header("Access-Control-Allow-Headers", "x-requested-with")
        # 请求方式
        self.set_header("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE")

        self.set_header('Access-Control-Allow-Headers', 'Content-Type')

        self.set_header('Content-Type', 'application/json; charset=UTF-8')

        print('头部设置成功')

    def post(self):
        self.write('这里是post请求')

    def get(self):
        self.write('这里是get请求')

    def delete(self):
        self.write('这里是delete请求')

    def put(self):
        self.write('这里是put请求')

    def options(self, *args):
        # 设置状态码
        self.set_status(204)
        # 设置拦截
        self.finish()


def ret_json(mydict):
    # indent 缩进方式
    res = json.dumps(mydict, ensure_ascii=False, default=str, indent=4)

    return res


# 异步监听器
async def reader(channel):
    while True:

        async with async_timeout.timeout(1):

            # 获取频道发布的消息
            message = await channel.get_message(ignore_subscribe_messages=True)

            if message is not None:

                # 遍历用户,推送消息
                for user in users:

                    # 值推送订阅了相关频道的用户
                    if user.get_cookie('channel') == message['channel']:
                        user.write_message(message['data'])


# 异步redis发布订阅实例
async def setUp():
    # 建立异步连接
    r = await aioredis.from_url('redis://localhost', decode_responses=True)

    # 获取发布订阅的实例
    pubsub = r.pubsub()

    # 开始订阅   异步订阅
    await pubsub.subscribe(channels)

    # 异步监听   创建异步任务
    asyncio.create_task(reader(pubsub))


class Msgl:
    # 记录当前的链接用户
    def user_connect(self, user):

        r = redis.Redis(decode_responses=True)
        # r = redis.Redis(decode_responses=True)

        channel = user.get_cookie('channel')

        x = r.hget('channels', channel)

        if not x:
            r.hset('channels', channel, 1)

        get_channel = r.hget('channels', channel)

        if int(get_channel) < 2:
            users.append(user)
            r.hset('channels', channel, (int(get_channel) + 1))
        else:
            user.write_message('客服正忙稍后再试')

    # 断开链接
    def user_del(self, user):

        # r = await aioredis.from_url('redis://localhost',decode_responses=True)
        r = redis.Redis(decode_responses=True)

        get_channel = r.hget('channels', user.get_cookie('channel'))

        r.hset('channels', user.get_cookie('channel'), (int(get_channel) - 1))

        users.remove(user)

    # 发消息
    def send(self, message):
        for user in users:
            user.write_message({'msg': message})


class WB(tornado.websocket.WebSocketHandler):
    # 跨域支持
    def check_origin(self, origin):
        return True

    # 开启链接
    def open(self):
        # users.append(self)
        # for i in users:
        #     # print(i)
        #     print(i.get_cookie('channel'), '你好')
        Msgl().user_connect(self)

    # 接收消息
    def on_message(self, message):
        # print(message)
        self.write_message(message)

    # 断开
    def on_close(self):
        Msgl().user_del(self)


# 接口 发布消息
class Msg(BaseHandler):

    # 发布信息
    # @pass_window  # 固定窗口限流
    # @pass_slide_window
    async def get(self):
        data = self.get_argument('data', None)

        channel = self.get_argument('channel', None)

        print(data, channel)

        # r = await aioredis.from_url('redis://localhost',decode_responses=True)
        #
        # res = await r.lrange('channel', 0, -1)
        # if channel not in res:
        #     await r.lpush('channel', channel)
        # else:
        #     pass
        #
        # info = eval(data)
        #
        # if len(info['send']['filename']) > 300 or len(info['send']['filename']) < 1:
        #     return self.write(ret_json({'code': 403, 'meg': '长度不符'}))
        #
        # # 发布消息
        #
        # await r.publish(channel, data)
        # # 单个
        # # mystack = MyStack()
        # # 多个库
        # mystacks = MyStacks()
        # data1 = json.loads(data)
        # data1['time'] = time.time()
        # data1 = json.dumps(data1)
        # # 单个
        # # mystack.push('send', data1)
        # # 多个库
        # mystacks.push(channel, data1)

        # 延迟队列删除最早的信息
        # Delay_out().add(id)

        return self.write({"code": 200, "msg": data})

    # 获取数据
    # @pass_window
    # @pass_slide_window
    async def post(self):
        # mystack = MyStack()
        # res = mystack.lrange('send')

        channel = self.get_argument('channel', None)

        id = self.get_argument('id', None)

        mystacks = MyStacks()

        res = mystacks.lrange(channel)

        list1 = []
        for i in json.loads(res):
            print(i)

            if int(i['time']) + 30 > int(time.time()):

                i['state'] = 1

                list1.append(i)
            else:
                list1.append(i)

        return self.write(ret_json(list1))

    # 机器人对话存储
    async def put(self):
        data = self.get_argument('data', None)
        user = self.get_argument('user', None)
        id = self.get_argument('id', None)
        res = qinyunke(data)

        r = await aioredis.from_url('redis://localhost', decode_responses=True)
        print(res, '机器人')

        await r.lpush('robot%s' % id, str({'user': user, 'send': data}))
        await r.lpush('robot%s' % id, str({'user': '机器人', 'send': res}))

        return self.write(ret_json({'code': 200, 'meg': res}))

    # 获取与机器之前的对话信息
    async def delete(self):
        id = self.get_argument('id', None)

        r = await aioredis.from_url('redis://localhost', decode_responses=True)

        res = await r.lrange('robot%s' % id, 0, -1)

        res = [eval(i) for i in res]

        res.reverse()

        return self.write(ret_json(res))


class Test(BaseHandler):
    async def get(self):
        return self.write(ret_json({'code': 200, 'meg': '你好', 's': 'hello world!'}))


# 建立tornado实例
app = tornado.web.Application(
    [
        (r'/send/', Msg),
        (r'/wb/', WB),
        (r'/', Test),

    ],
    debug=True

)

if __name__ == '__main__':
    print('服务已启动')
    app.listen(8001)

    # 获取当前的事件循环对象
    loop = tornado.ioloop.IOLoop.current()

    # 回调方法   将监听的添加到事件循环

    loop.add_callback(setUp)

    loop.start()
